﻿using Microsoft.IdentityModel.Claims;
using Sustainalytics.Claims;
using Sustainalytics.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sustainalytics.Utils;

namespace Sustainalytics.DynamicsCRMGateway
{
    public class AccountClaimsRepository : ICrmReadOnlyRepository<UserClaim>
    {
        private static string _issuer = "Sustainalytics";
        private string _connectionString;
        public AccountClaimsRepository(string connectionString)
        {
            _connectionString = connectionString;
        }
    
        public IEnumerable<UserClaim> ReadAll()
        {
            using (var serviceContext = new CrmServiceContextFactory().GetCrmServiceContext(_connectionString))
            {
                var directLinkedAccountClaimQuery = from a in serviceContext.AccountSet
                                                    join af in serviceContext.AccountFeatureSet on a.AccountId equals af.AccountRef.Id
                                                    join f in serviceContext.FeatureSet on af.FeatureRef.Id equals f.FeatureId
                                                    join fc in serviceContext.FeatureClaimBridgeSet on f.FeatureId equals fc.FeatureId
                                                    join c in serviceContext.ClaimSet on fc.ClaimId equals c.ClaimId
                                                    where a.Status == (int)Status.Active
                                                    select new
                                                    {
                                                        AccountName = a.Name,
                                                        AccountId = a.Id,
                                                        FeatureId = f.FeatureId,
                                                        ClaimInfo = new System.Security.Claims.Claim(c.Name, c.Value, ClaimValueTypes.String, _issuer)
                                                    };

                var packageLinkedAccountClaimQuery = from a in serviceContext.AccountSet
                                                     join af in serviceContext.AccountFeatureSet on a.AccountId equals af.AccountRef.Id
                                                     join f_pack in serviceContext.FeatureSet on af.FeatureRef.Id equals f_pack.FeatureId
                                                     join f_child in serviceContext.FeatureSet on f_pack.FeatureId equals f_child.ParentFeatureRef.Id
                                                     join fc in serviceContext.FeatureClaimBridgeSet on f_child.FeatureId equals fc.FeatureId
                                                     join c in serviceContext.ClaimSet on fc.ClaimId equals c.ClaimId
                                                     where a.Status == (int)Status.Active
                                                     select new
                                                     {
                                                         AccountName = a.Name,
                                                         AccountId = a.Id,
                                                         ClaimInfo = new System.Security.Claims.Claim(c.Name, c.Value, ClaimValueTypes.String, _issuer)
                                                     };

                // Workaround for avoiding the CRM linq limmitations: 
                // - get the parent features from CRM
                // - filter directLinkedAccountClaim by excluding the parentFeatures with accidental claims (on client side)
                var parentFeatures = (from f_parent in serviceContext.FeatureSet
                                      join f_child in serviceContext.FeatureSet on f_parent.FeatureId equals f_child.ParentFeatureRef.Id
                                      select f_parent.FeatureId.Value).Distinct().ToList();

                var directLinkedAccountClaimQueryParentsRemoved = directLinkedAccountClaimQuery.ToList().Where(x => !parentFeatures.Contains(x.FeatureId.Value)).Select(x => new
                                                    {
                                                        AccountName = x.AccountName,
                                                        AccountId = x.AccountId,
                                                        ClaimInfo = x.ClaimInfo
                                                    });

                var directLinkedAccountClaims = directLinkedAccountClaimQueryParentsRemoved.ToList();
                directLinkedAccountClaims.ForEach(a =>
                {
                    var sb = new StringBuilder();
                    sb.Append("Type:");
                    sb.Append(a.ClaimInfo.Type);
                    sb.Append(" Value:");
                    sb.Append(a.ClaimInfo.Value);
                    sb.Append(",");
                    CommonEventSource.Log.CRMReadAccountDirectlyLinkedClaims(a.AccountName, sb.ToString());
                });

                var packageLinkedAccountClaims = packageLinkedAccountClaimQuery.ToList();
                packageLinkedAccountClaims.ForEach(a =>
                {
                    var sb = new StringBuilder();
                    sb.Append("Type:");
                    sb.Append(a.ClaimInfo.Type);
                    sb.Append(" Value:");
                    sb.Append(a.ClaimInfo.Value);
                    sb.Append(",");
                    CommonEventSource.Log.CRMReadAccountPackageLinkedClaims(a.AccountName, sb.ToString());
                });

                var accounts = directLinkedAccountClaims.Union(packageLinkedAccountClaims);

                // Workaround for avoiding the CRM linq limmitations: 
                // - group by on the client side
                return from ac in accounts
                       group ac.ClaimInfo by new { ac.AccountId, ac.AccountName } into g
                       select new UserClaim() { Id = g.Key.AccountId, Account = g.Key.AccountName, Claims = g.Distinct(new ClaimComparer()).ToList() };
            }
        }
    }
}
